package com.atguigu.tingshu.account.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.account.mapper.RechargeInfoMapper;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.model.account.RechargeInfo;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

    @Autowired
    private UserAccountMapper userAccountMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private UserAccountDetailMapper userAccountDetailMapper;

    @Autowired
    private RechargeInfoMapper rechargeInfoMapper;


    @Override
    public IPage<UserAccountDetail> findUserConsumePage(Page<UserAccountDetail> userAccountDetailPage, Long userId) {
        //  userId = ? and trade_type= 1204
        LambdaQueryWrapper<UserAccountDetail> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserAccountDetail::getUserId, userId);
        wrapper.eq(UserAccountDetail::getTradeType, "1204");
        wrapper.orderByDesc(UserAccountDetail::getId);
        return userAccountDetailMapper.selectPage(userAccountDetailPage, wrapper);
    }

    @Override
    public IPage<UserAccountDetail> findUserRechargePage(Page<UserAccountDetail> userAccountDetailPage, Long userId) {
        //  userId = ? and trade_type= 1201
        LambdaQueryWrapper<UserAccountDetail> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserAccountDetail::getUserId, userId);
        wrapper.eq(UserAccountDetail::getTradeType, "1201");
        wrapper.orderByDesc(UserAccountDetail::getId);
        return userAccountDetailMapper.selectPage(userAccountDetailPage, wrapper);
    }

    @Override
    public void rechargePaySuccess(String orderNo) {
        //  1.  修改充值状态
        //        RechargeInfo rechargeInfo = new RechargeInfo();
        //        rechargeInfo.setRechargeStatus(SystemConstant.ORDER_STATUS_PAID);
        LambdaQueryWrapper<RechargeInfo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(RechargeInfo::getOrderNo, orderNo);
        //  this.rechargeInfoMapper.update(rechargeInfo,wrapper);

        //  根据orderNo 查询 rechargeInfo
        RechargeInfo rechargeInfo = this.rechargeInfoMapper.selectOne(wrapper);
        //  判断
        if (rechargeInfo.getRechargeStatus().equals(SystemConstant.ORDER_STATUS_PAID)) {
            return;
        }
        rechargeInfo.setRechargeStatus(SystemConstant.ORDER_STATUS_PAID);
        rechargeInfoMapper.updateById(rechargeInfo);
        //  2.  给当前账户添加充值金额;
        this.add(rechargeInfo.getUserId(), rechargeInfo.getRechargeAmount(), rechargeInfo.getOrderNo(), SystemConstant.ACCOUNT_TRADE_TYPE_DEPOSIT, "充值");
    }

    @Transactional(rollbackFor = Exception.class)
    public void add(Long userId, BigDecimal amount, String orderNo, String tradeType, String title) {
        //  1.  user_account userId = ? total_amount+? available_amount+? total_income_amount + ?;
        //  查询userId
        //        LambdaQueryWrapper<UserAccount> wrapper = new LambdaQueryWrapper<>();
        //        wrapper.eq(UserAccount::getUserId,userId);
        //        UserAccount userAccount = userAccountMapper.selectOne(wrapper);
        //        BigDecimal totalAmount = userAccount.getTotalAmount().add(rechargeAmount);
        //        userAccount.setTotalAmount(totalAmount);
        //        userAccountMapper.updateById(userAccount);
        userAccountMapper.add(amount, userId);
        //  2.  记录账户明细
        this.log(userId, title, tradeType, amount, orderNo);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unlock(String orderNo) {
        //  先声明两个key 防止重复消费. 数据key;
        // 防止重复消费
        String key = "unlock:" + orderNo;
        // 数据key
        String dataKey = "account:lock:" + orderNo;
        Boolean exist = this.redisTemplate.opsForValue().setIfAbsent(key, orderNo, 5, TimeUnit.MINUTES);
        //  执行失败，有人操作
        if (!exist) {
            return;
        }
        //  获取解锁金额对象
        String strJson = (String) this.redisTemplate.opsForValue().get(dataKey);
        //  判断是否存在
        if (StringUtils.isEmpty(strJson)) {
            this.redisTemplate.delete(key);
            return;
        }
        //  进行数据类型转换
        AccountLockResultVo accountLockResultVo = JSON.parseObject(strJson, AccountLockResultVo.class);
        //  实现扣减金额
        int count = userAccountMapper.unlock(accountLockResultVo.getAmount(), accountLockResultVo.getUserId());
        //  判断
        if (count == 0) {
            this.redisTemplate.delete(key);
            return;
        }
        //  解锁成功.
        this.log(accountLockResultVo.getUserId(), "解锁" + accountLockResultVo.getContent(), SystemConstant.ACCOUNT_TRADE_TYPE_UNLOCK, accountLockResultVo.getAmount(), orderNo);
        //  要将这个数据删除.
        this.redisTemplate.delete(dataKey);

    }

    /**
     * 扣减金额
     *
     * @param orderNo
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void minus(String orderNo) {
        //  防止重复操作
        String key = "minus:" + orderNo;
        //  检查锁定的时候，将userId,amount,conent 在这个对象中AccountLockResultVo 放入缓存了。
        String dataKey = "account:lock:" + orderNo;
        //  获取数据.
        Boolean exist = this.redisTemplate.opsForValue().setIfAbsent(key, orderNo, 5, TimeUnit.MINUTES);
        //  判断
        if (!exist) {
            return;
        }

        String strJson = (String) this.redisTemplate.opsForValue().get(dataKey);
        //  判断是否存在
        if (StringUtils.isEmpty(strJson)) {
            this.redisTemplate.delete(key);
            return;
        }
        //  进行数据类型转换
        AccountLockResultVo accountLockResultVo = JSON.parseObject(strJson, AccountLockResultVo.class);
        //  实现扣减金额
        int count = userAccountMapper.minus(accountLockResultVo.getAmount(), accountLockResultVo.getUserId());
        //  判断
        if (count == 0) {
            this.redisTemplate.delete(key);
            return;
        }
        //  扣减成功.
        this.log(accountLockResultVo.getUserId(), "扣减" + accountLockResultVo.getContent(), SystemConstant.ACCOUNT_TRADE_TYPE_MINUS, accountLockResultVo.getAmount(), orderNo);

        //  要将这个数据删除.
        this.redisTemplate.delete(dataKey);
    }

    /**
     * @param accountLockVo
     * @return 为了能够在后面的减少金额时，直接从缓存中获取数据。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AccountLockResultVo checkAndLock(AccountLockVo accountLockVo) {
        //	定义一个锁的key
        String key = "checkAndLock:" + accountLockVo.getOrderNo();
        //	数据key 用来存储 accountLockResultVo 后续都检查与锁定成功会将这个数据存储到缓存.
        String dataKey = "account:lock:" + accountLockVo.getOrderNo();
        //	使用 setnx 防止重复 setnx key value; 当key 不存在的时候，生效.如果key存在了，则执行失败.
        //  注意要给一个过期时间，防止出现key 永久存在。
        Boolean exist = this.redisTemplate.opsForValue().setIfAbsent(key, accountLockVo.getOrderNo(), 5, TimeUnit.MINUTES);
        //	判断
        if (!exist) {
            //	执行失败.
            //  accountLockResultVo = (AccountLockResultVo) this.redisTemplate.opsForValue().get(dataKey);
            String strJson = (String) this.redisTemplate.opsForValue().get(dataKey);
            if (!StringUtils.isEmpty(strJson)) {
                return JSON.parseObject(strJson, AccountLockResultVo.class);
            } else {
                return null;
            }
            //	判断 accountLockResultVo
            //            if (null != accountLockResultVo) {
            //                //	返回数据
            //                return accountLockResultVo;
            //            } else {
            //                //	提示信息.
            //                return null;
            //            }

        }
        //	调用检查方法.
        UserAccount userAccount = userAccountMapper.check(accountLockVo.getAmount(), accountLockVo.getUserId());
        //	判断
        if (userAccount == null) {
            //  检查失败:
            this.redisTemplate.delete(key);
            return null;
        }
        //  检查通过,锁定金额
        int count = userAccountMapper.lock(accountLockVo.getAmount(), accountLockVo.getUserId());
        //  判断
        if (count == 0) {
            //  检查失败:
            this.redisTemplate.delete(key);
            return null;
        }
        //  存储记录.
        //  声明对象
        AccountLockResultVo accountLockResultVo = new AccountLockResultVo();
        accountLockResultVo.setUserId(accountLockVo.getUserId());
        accountLockResultVo.setAmount(accountLockVo.getAmount());
        accountLockResultVo.setContent(accountLockVo.getContent());
        //  this.redisTemplate.opsForValue().set(dataKey,accountLockResultVo);
        this.redisTemplate.opsForValue().set(dataKey, JSON.toJSONString(accountLockResultVo), 5, TimeUnit.MINUTES);
        //  记录账户明细
        this.log(accountLockVo.getUserId(), "锁定：" + accountLockVo.getContent(), SystemConstant.ACCOUNT_TRADE_TYPE_LOCK, accountLockVo.getAmount(), "lock:" + accountLockVo.getOrderNo());
        //  返回数据
        return accountLockResultVo;
    }

    private void log(Long userId, String title, String tradeType, BigDecimal amount, String orderNo) {
        //  声明对象
        UserAccountDetail userAccountDetail = new UserAccountDetail();
        userAccountDetail.setUserId(userId);
        userAccountDetail.setTitle(title);
        userAccountDetail.setTradeType(tradeType);
        userAccountDetail.setAmount(amount);
        userAccountDetail.setOrderNo(orderNo);
        //  保存数据
        userAccountDetailMapper.insert(userAccountDetail);
    }

    @Override
    public BigDecimal getAvailableAmount(Long userId) {
        //	构建查询条件
        LambdaQueryWrapper<UserAccount> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserAccount::getUserId, userId);
        UserAccount userAccount = userAccountMapper.selectOne(wrapper);
        //	判断
        if (null == userAccount) {
            return new BigDecimal("0");
        }
        //	返回可用金额
        return userAccount.getAvailableAmount();
    }

    @Override
    public void addUserAccount(Long userId) {
        //	创建账户信息对象
        UserAccount userAccount = new UserAccount();
        userAccount.setUserId(userId);
        //	为了方便后续测试购买专辑等
        userAccount.setTotalAmount(new BigDecimal("1000"));
        userAccount.setAvailableAmount(new BigDecimal("1000"));
        userAccountMapper.insert(userAccount);
    }
}
